本文是有關 Redis 的學習筆記的一部分,相關目錄請參考 Redis 學習筆記(1)-簡介。
- 上一篇 客戶端整合 Redis
- 下一篇 客戶端整合 Sentinel
上一篇我們說明如何在Spring Boot內連結到Redis伺服器內,當時是使用單機模式的Redis,但是在企業內使用系統服務時,會非常注重服務的高可用性,帶有單點失效瑕疵的單機模式是不被接受的。在Redis架構中除了單機模式外還有兩種,分別是叢集(cluster)和主從複製(master-replica),下面我們分別說明。
Redis叢集模式是將資料分散在不同的主節點上,客戶端連上任一主節點上就可以讀寫任何鍵,而計算目的鍵是位於那一個主節點上則是由Redis叢集來計算。其示意圖如下:
Redis叢集的目標應該是作資料的水平擴容,資料是分散在不同主節點上,主節點再將資料複製到從節點。若是主節點遺失,則會將主節點對應的從節點提昇為主節點。下圖為Redis1遺失後Redis4被提昇為主節點的示意圖。
Redis主從複製模式是在一群節點中,其中一個是主節點,其他為從節點,客戶端連線到主節點,執行讀寫操作,而資料異動則持續由主節點複製到從節點。其示意圖如下:
上面的描述會衍生二個問題,一是客戶端如何知道那一個Redis伺服器是主節點,二是若主節點遺失後,如何產出新的主節點讓服務繼續。這二個問題是透過Sentinel來解決,Sentinel也是以一個叢集存在,Sentinel叢集內的節點會監看所有的Redis主從節點,若Redis主節點遺失,Sentinel叢集內的節點會選出下一個主節點,被選出的從節點就提昇為主節點,同時客戶端也可以詢問Sentinel叢集而得知最新的主節點。其示意圖如下:
下面這張圖則表示在Redis1遺失後,Sentinel叢集確認後並協助選出Redis2為主節點,Redis2提昇為主節點,客戶端透過詢問Sentinel叢集得知最新主節點後,將客戶端的連線轉到Redis2上。
在已知Redis叢集模式和Redis主從複製模式這二個模式都具備高可用性,那應該使用那一種模式呢?這必須依實際情況來判斷,Redis叢集雖然有較大的資料容量,但其也有其他缺點,如在一個指令含多鍵操作時,只有在這些鍵全位於同一個Redis下才會有操作原子性。
對我而言,Redis主從複製模式比較符合我的需求,所以我只針對這個模式作測試。
在決定採用Redis主從複製模式後,那Redis和Sentinel該部署在何種環境上?可以選擇部置在Linux VM主機上,也可以部署在Docker或Docker Swarm上。我的決定是使用Docker Swarm,因為使用容器技術可以省去軟體安裝和函式庫環境相容性的問題,而使用Swarm則可以由一架主機上下指令來帶起其他Docker主機上的服務。
下圖是我的測試架構,準備三架 VM 主機,每架主機上皆啟動 Docker 環境,在每個 Docker 環境中各啟動一個 Redis 和 Sentinel。 啟動的 Redis (Master/Replica) 皆是 Swarm 內的服務,且該服務必須固定在指定的 Swarm 節點上,不可任意遷移。
瞭解測試環境架構後,接下來先建立Docker Swarm環境。
我們不說明如何建立VM及在其建立Docker環境,直接假設這些是已備好的資源,在這些前題上建立Docker Swarm環境。
建立Docker Swarm環境的動作分解下面項目:
我們先由準備VM主機開始。
準備三架 VM 主機,已安裝好 Docker 環境,其相關資訊(IP/Hostname)如下:
在之後的說明或設定中,會直接使用上面的主機名稱或IP。
每架主機上皆必須針對之後會用到的埠號作開啟防火牆的操作,其操作指令如下:
firewall-cmd --add-service=docker-swarm --permanent
firewall-cmd --add-service=redis --permanent
# port 26379 為 Sentinel 使用
firewall-cmd --add-port=26379/tcp --permanent
firewall-cmd --reload
firewall-cmd --list-all
接下來我們要將三個獨立的Docker節點整合成一個Swarm叢集,其指令如下:
若原 Docker 仍屬於舊集群可用下列指令將其移除
docker swarm leave -f
在 c7a1 上初始化一個新集群
docker swarm init --advertise-addr 10.0.2.11
取得加入 Swarm master 的指令:
docker swarm join-token manager
指令產出類似下方結果:
[root@c7a1 ~]# docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-1fvir6k61i4ayua1ycjc7ik7j43ap3uxe7hfkbr6pyux9l6e0c-90vlcfmhvm0sin7hjd4r5j60u 10.0.2.11:2377
[root@c7a1 ~]#
在 c7a2,c7a3 主機上分別執行上面產生的指令
docker swarm join --token SWMTKN-1-1fvir6k61i4ayua1ycjc7ik7j43ap3uxe7hfkbr6pyux9l6e0c-90vlcfmhvm0sin7hjd4r5j60u 10.0.2.11:2377
完成後可在 c7a1,c7a2,c7a3 任一架主機執行下列指令,檢視集群成員
docker node ls
在預備架設 Redis + Sentinel 架構中,每個 Redis 皆是固定在指定的節點上。所以需要在每個 Docker 節點上加入唯一的識別標籤。
執行 Docker 節點標籤設定的方式如下:
## 執行標籤設定
docker node update --label-add name=c7a1 c7a1
docker node update --label-add name=c7a2 c7a2
docker node update --label-add name=c7a3 c7a3
檢查設定結果
## 檢查目前標籤設定值
docker node ls -q | xargs docker node inspect -f '{{ .ID }} [{{ .Description.Hostname }}]: {{ .Spec.Labels }}'
在擁有Swarm環境後,可以在其上建立Redis和Sentinel服務,雖然這二個是不同的Swarm服務,但是我們放在一起說明。
建立Redis和Sentinel的Swarm服務,分為下列幾個操作:
Redis 為確保資料在重啟容器後不遺失,就必須將資料寫到 Volume 中。其架構如下圖:
所以必須在每個節點建立 Volume,因為 Volume 是固定在指定的節點,所以對應的 Redis 也必須啟動固定的節點(這也就是之前在每個節點建立唯一標籤的原因)。
建立 Volume 指令如下,該指令必須在 c7a1/c7a2/c7a3 皆執行。
# 清除未使用的 Volume
docker volume prune -f
# 建立 Volume
docker volume create redis
# 建立 redis & sentinel conf 使用的目錄
mkdir -p /var/lib/docker/volumes/redis/_data/conf
# 建立 redis data 使用的目錄
mkdir -p /var/lib/docker/volumes/redis/_data/data
# 參考 docker hub / redis 的 Docker file, 其執行時使用 uid 999 故要更改 volume 目錄的權限
chown -R 999:999 /var/lib/docker/volumes/redis/_data
# 檢視當前的 Volume
docker volume ls
計劃當 Redis 容器啟動後會將 Volume redis 掛載在 Redis 容器內,所以我們可以直將 Redis 使用的設定檔直接放在 Volume redis 內。當 Redis 容器起動後就可直接讀取容器內 Volume 內的設定檔。
預定先讓 c7a1 上的 Redis 為 Master,而 Master 的設定檔和 Replica 是有小部份不同。
在 c7a1 的節點上執行設定檔配置的指令
## 直接將 Master Redis 設定檔寫到 Volume redis 內的空間
cat << EOF | tee /var/lib/docker/volumes/redis/_data/conf/redis.conf
port 6379
bind 0.0.0.0
dir /redis/data/
protected-mode yes
requirepass mypwd
dbfilename dump.rdb
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
masterauth mypwd
replica-priority 100
EOF
重設該設定檔的 uid:gid,讓 redis容器可以存取。
# 參考 docker hub / redis 的 Docker file, 其執行時使用 uid 999 故要更改 volume 目錄的權限
chown -R 999:999 /var/lib/docker/volumes/redis/_data
預定 c7a2 和 c7a3 上的 Redis 為 Replica,而 Replica 的設定檔比 Master 多了一項 slaveof
設定。
在 c7a2/c7a3 的節點上執行設定檔配置的指令
## 設定 Master 為 c7a1
MASTER_IP=10.0.2.11
## 建立 redis (slave1) 設定檔
cat << EOF | tee /var/lib/docker/volumes/redis/_data/conf/redis.conf
port 6379
bind 0.0.0.0
dir /redis/data/
protected-mode yes
requirepass mypwd
dbfilename dump.rdb
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
slaveof ${MASTER_IP} 6379
masterauth mypwd
replica-priority 101
EOF
重設該設定檔的 uid:gid,讓 redis容器可以存取。
# 參考 docker hub / redis 的 Docker file, 其執行時使用 uid 999 故要更改 volume 目錄的權限
chown -R 999:999 /var/lib/docker/volumes/redis/_data
計劃 Sentinel 和 Redis 一樣,Sentinel 啟動後就會將 Volume redis 掛載在 Sentinel 容器。所以 Sentinel 的設定檔也可以直接放在 Volume redis 內。當 Sentinel 容器起動後就可直接讀取容器內 Volume 內的設定檔。
對 Sentinel 而言,沒有主從之分,所以在 c7a1/c7a2/c7a3 上的設定檔應該是完全相同。
在 c7a1/c7a2/c7a3 的節點上執行設定檔配置的指令,如下:
## 設定 Master 為 c7a1
MASTER_IP=10.0.2.11
## 建立 sentinel 設定檔
cat << EOF | tee /var/lib/docker/volumes/redis/_data/conf/sentinel.conf
port 26379
sentinel resolve-hostnames yes
sentinel monitor mymaster ${MASTER_IP} 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel auth-pass mymaster mypwd
EOF
重設該設定檔的 uid:gid,讓 redis容器可以存取。
# 參考 docker hub / redis 的 Docker file, 其執行時使用 uid 999 故要更改 volume 目錄的權限
chown -R 999:999 /var/lib/docker/volumes/redis/_data
在完成 Redis 和 Sentinel 的設定檔後,還須有 Swarm 部署服務用的 compose 檔案。該檔僅需放在 c7a1 主機上,計劃由 c7a1 主機來啟動 Swarm 上的服務。
計劃將 Redis 和 Sentinel 分成二個不同的 Swarm 服務,所以必須準備不同的 compose 檔案。分別為:
redis.yml
sentinel.yml
在實測過程中,發現使用 redis:7 和 redisson:3.17.3 搭配時,無法使用在 Sentinel 的環境下。觀察到的現像為 redisson 和 Sentinel 建立連線後會詢問從機資訊,其使用的指令為 sentinel slaves
。但是在 redis:7 該指令已移除,改用指令 sentinel replicas
,redisson:3.17.3 無法支援新指令。最後只能暫時放棄 redis:7 改用 redis:6.2.3。
在Redis官網上,有說明若使用Docker環境時,Docker網路應選用host模式。
在 c7a1 上建立 compose 檔案,如下:
cd /root
mkdir -p /root/redis_stack
cd /root/redis_stack
cat << EOF | tee /root/redis_stack/redis.yml
version: "3.6"
services:
master:
image: "redis:6.2.3"
command: redis-server /redis/conf/redis.conf
networks:
hostnet: {}
volumes:
- redis:/redis
deploy:
replicas: 1
placement:
constraints:
- "node.labels.name==c7a1"
replica1:
image: "redis:6.2.3"
command: redis-server /redis/conf/redis.conf
networks:
hostnet: {}
volumes:
- redis:/redis
deploy:
replicas: 1
placement:
constraints:
- "node.labels.name==c7a2"
replica2:
image: "redis:6.2.3"
command: redis-server /redis/conf/redis.conf
networks:
hostnet: {}
volumes:
- redis:/redis
deploy:
replicas: 1
placement:
constraints:
- "node.labels.name==c7a3"
volumes:
redis:
external: true
networks:
hostnet:
external: true
name: host
EOF
在 c7a1 上建立 compose 檔案,如下:
cd /root
mkdir -p /root/redis_stack
cd /root/redis_stack
cat << EOF | tee /root/redis_stack/sentinel.yml
version: "3.6"
services:
sent0:
image: "redis:6.2.3"
command: redis-sentinel /redis/conf/sentinel.conf
networks:
hostnet: {}
volumes:
- redis:/redis
deploy:
replicas: 1
placement:
constraints:
- "node.labels.name==c7a1"
sent1:
image: "redis:6.2.3"
command: redis-sentinel /redis/conf/sentinel.conf
networks:
hostnet: {}
volumes:
- redis:/redis
deploy:
replicas: 1
placement:
constraints:
- "node.labels.name==c7a2"
sent2:
image: "redis:6.2.3"
command: redis-sentinel /redis/conf/sentinel.conf
networks:
hostnet: {}
volumes:
- redis:/redis
deploy:
replicas: 1
placement:
constraints:
- "node.labels.name==c7a3"
volumes:
redis:
external: true
networks:
hostnet:
external: true
name: host
EOF
完成所有設定後,接下來需要瞭解的是啟動服務。啟動服務的次序為先啟動 Redis 服務,服務正常後再啟動 Sentinel 服務。
啟動 Redis 服務的方式如下:
cd /root/redis_stack
## 啟動服務將其命名為 redis
docker stack deploy -c redis.yml redis
## 檢查 docker stack 帶起來的服務
docker stack ls
## 檢查 docker stack 內 redis 服務的狀態
docker stack ps redis
## 檢查 docker swarm 上的服務
docker service ls
啟動 Sentinel 服務的方式如下:
cd /root/redis_stack
## 啟動服務將其命名為 sentinel
docker stack deploy -c sentinel.yml sentinel
## 檢查 docker stack 帶起來的服務
docker stack ls
## 檢查 docker stack 內 sentinel 服務的狀態
docker stack ps sentinel
Redis+Sentinel 服務啟動後,可以作簡單的驗證。
當前的 Master 為 c7a1,驗證在 c7a1 寫入資料是否可以在 c7a2 見到相同的資料。
在 c7a1 執行登入 redis 並寫入一筆資料
在 c7a1 進入 redis 容器並建立連線
## 進入 redis 容器
docker exec -it `docker ps -q -f 'name=redis'` /bin/bash
## 進入容器後,再連線到本地的 redis
redis-cli -a mypwd
設定一筆資料,並註明是由 c7a1 設定的
set k1 v1_from_c7a1
在 c7a2 執行登入 redis 並檢查剛寫入的資料
## 進入 redis 容器
docker exec -it `docker ps -q -f 'name=redis'` /bin/bash
## 進入容器後,再連線到本地的 redis
redis-cli -a mypwd
## 檢查剛寫入的資料
get k1
Sentinel 主要服務二個功能。第一個功能是回覆客戶端當前 Master 主機的位置(IP+PORT)。第二個功能是監控 Master 是否正常,若發現異常則作切換。
連上 Sentinel 查找 Master 主機位置
在 c7a1/c7a2/c7a3 登入 Sentinel 並詢問 Master
## 進入 sentinel 容器
docker exec -it `docker ps -q -f 'name=sentinel'` /bin/bash
## 進入容器後,再連線到本地的 sentinel
redis-cli -p 26379
## 詢問 Master
sentinel get-master-addr-by-name mymaster
## 上述詢問會得到 Master 的 IP + POrt
關閉 Master 進程,確認切換發生,Replica 主機提昇為 Master 主機
連上 Master(應為 c7a1),中止 Master 程序
## 在 Master 主機上停止 redis 進程
docker stop `docker ps -q -f 'name=redis'`
連上任一個 Sentinel 詢問 Master
## 進入 sentinel 容器
docker exec -it `docker ps -q -f 'name=sentinel'` /bin/bash
## 進入容器後,再連線到本地的 sentinel
redis-cli -p 26379
## 詢問 Master
sentinel get-master-addr-by-name mymaster
## 上述詢問會得到 Master 的 IP + POrt
在完成測試後,要移除所有的測試資源,其操作非常簡單,僅需下列指令操作即可:
移除服務的方式如下:
## 移除 sentinel 服務
docker stack rm sentinel
## 移除 redis 服務
docker stack rm redis
在本篇學習中,我們介紹了Redis兩種高可用模式,一種是Redis叢集模式,另一種是Redis主從複製模式。
我們也說明了測試環境架構,和如何搭建我們的測試環境,其中包涵了Docker Swarm的搭建和在Swarm上搭建Redis和Sentinel的服務。
最後我們驗證了Redis和Sentinel服務皆符合我們的預期。